技巧26 语言环境管理

除时区之外,在构建镜像或运行容器时与Docker镜像相关的另一方面是语言环境。

注意

语言环境定义了程序应当使用的语言和国家/地区设置。一般来说,人们会通过设置 LANGLANGUAGElocale-gen 等环境变量来设置语言环境,以及通过以 LC_ 开头的变量(如 LC_TIME )在环境中设置确定如何向用户显示时间。

注意

编码(在这个上下文里)即是将文本作为字节存储在一台计算机上的方式。读者值得花些时间来理解这个主题,因为它往往出现在各种各样的上下文里。

问题

用户在应用程序构建或部署时看到编码错误。

解决方案

确保在Dockerfile里设置了正确的指定语言的环境变量。编码问题对于所有用户而言并不总是显而易见的,但是在构建应用的时候它们往往是致命的。代码清单4-15给出的是在Docker中构建应用程序时典型的编码错误的几个示例。

代码清单4-15 典型的编码错误

MyFileDialog:66: error: unmappable character for encoding ASCII
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa0' in
➥ position 20: ordinal not in range(128)

这些错误可能会导致构建失败或是应用程序挂掉。

提示

在发现报错时需要注意的关键词列表(可能还有其他的)有“encoding”“ascii”“unicode”“UTF-8”“character”及“codec”等。如果用户在错误日志里有看到这些关键词,那么有可能遇到的就是一个编码问题。

这和Docker有什么关系?

在设置一个完整的操作系统时,通常会有一个引导程序引导用户完成设置过程,它会要求用户确认首选的时区、语言和键盘布局等。

如你所知,就目前来说,Docker容器一般针对的是特定用途,它提供的不是一个完整的操作系统。相反,它们(越来越)是运行应用程序的一个最小环境。因此。在默认情况下,它们可能不具备用户习惯使用的操作系统上的所有设置。

特别是,Debian在2011年删除了对locales包的依赖,这意味着在默认情况下,基于Debian镜像的容器里没有语言环境的设置。举个例子,代码清单4-16展示了一个Debian派生的Ubuntu镜像的默认环境。

代码清单4-16 一个Ubuntu容器的默认环境

$  docker run -ti ubuntu bash
root@d17673300830:/# env
HOSTNAME=d17673300830
TERM=xterm
LS_COLORS=rs=0 [...]
HIST_FILE=/root/.bash_history
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
SHLVL=1
HOME=/root
_=/usr/bin/envj

默认情况下,镜像里没有 LNAG 或类似的 LC_ 设置。代码清单4-17中展示了Docker宿主机的设置。

代码清单4-17 Docker宿主机操作系统上的LANG设定

$ env | grep LANG
LANG=en_GB.UTF-8

我们的shell里有一个 LANG 设置,它告知应用程序在终端里的首选编码是英式英语,文本用UTF-8编码。

为了演示一个编码问题,我们将在本地创建一个包含了用UTF-8编码的英国货币符号(英镑符号)的文件,然后根据终端编码的不同,展示该文件的解读内容是如何变化的,如代码清单4-18所示。

代码清单4-18 创建并展示一个用UTF8编码的英国货币符号

$ env | grep LANG
LANG=en_GB.UTF-8
$ echo -e "\xc2\xa3" > /tmp/encoding_demo  ⇽--- 使用带-e标志的echo命令将代表英镑符号的两字节输出到一个文件里
$ cat /tmp/encoding_demo  ⇽--- 
£  ⇽---  cat文件,我们会看到一个英镑符号

在UTF-8编码中,一个英镑符号由两字节表示。我们使用 echo -e\x 表示法输出这两个字节,然后将它重定向到一个文件里。当我们 cat 这个文件时,终端会读取这两字节,并且知道该如何将输出解释表达为一个英镑符号。

现在,如果我们改变终端的字符集,使用 Western(ISO Latin 1) 编码(同时也设置本地的 LANG 变量),然后输出该文件,该文件的输出结果看上去将会完全不同,如代码清单4-19所示。

代码清单4-19 用英国货币符号演示编码问题

$ env | grep LANG  ⇽--- 
LANG=en_GB.ISO8859-1  ⇽--- 由终端设置的LANG环境变量如今已经被设置为Western(ISO Latin 1)
$ cat /tmp/encoding_demo  ⇽--- 
£  ⇽--- 这两字节的展示结果有些不同,它们以单独的两个字符的形式呈现在我们面前

\x2 字节被翻译为一个大写的字母A并且顶部有一个抑扬符号,而 \xa3 字节则被翻译成一个英镑符号!

注意

这里我们故意说成“我们”而不是“你们”!调试和控制编码是一件棘手的事情,它可能依赖于正在运行的应用的状态、用户设置的环境变量、正在运行的应用程序本身以及创建检索数据的所有前置因素!

正如所见,编码可能会受到终端里编码集的影响。回到Docker,我们注意到我们的Ubuntu容器里并没有设置默认的编码环境变量。因此,在宿主机或容器里运行相同的命令时,用户可能会得到不同的结果。如果用户看到编码相关的报错,也许需要在Dockerfile里设置它们。

在Dockerfile里设置编码

我们现在来看看如何控制一个Debian系镜像的编码。我们选择这个镜像是因为它可能是更常见的上下文之一。代码清单4-20所示的这个例子将会设置一个简单的镜像,它只会输出默认的环境变量。

代码清单4-20 设置一个Dockerfile示例

 FROM ubuntu:16.04  ⇽--- 使用一个Debian派生的基础镜像
 RUN apt-get update && apt-get install -y locales  ⇽--- 更新软件包索引并且安装locales软件包
 RUN locale-gen en_US.UTF-8  ⇽--- 生成美式英语的locale文件,使用UTF-8编码
 ENV LANG en_US.UTF-8  ⇽--- 设置LANG环境变量
 ENV LANGUAGE en_US:en  ⇽--- 设置LANGUAGE环境变量
 CMD env  ⇽--- 默认命令env将会展示容器里的环境设置

用户可能想知道 LANGLANGUAGE 变量之间的区别是什么。简而言之, LANG 是首选语言和编码设置的默认设置。它也提供了一个应用查找更多指定 LC_* 这样的设置时的一个默认值。 LANGUAGE 则提供了一个有序的应用程序首选语言列表(如果主要语言不可用)。可以通过执行 man locale 获取更多信息。

现在读者可以构建该镜像,然后运行它,看看哪里有变动,如代码清单4-21所示。

代码清单4-21 构建和运行encoding镜像

$ docker build -t encoding .   ⇽--- 构建encoding Docker镜像
 [...]
$ docker run encoding  ⇽--- 运行构建好的Docker镜像
no_proxy=*.local, 169.254/16
LANGUAGE=en_US:en  ⇽--- 设置LANGUAGE环境变量
HOSTNAME=aa9d6c8a3ff5
HOME=/root
HIST_FILE=/root/.bash_history
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=en_US.UTF-8  ⇽--- 设置LANG 环境变量
PWD=/

讨论

与上面关于时区的技巧26类似,本技巧讲述了一个能让人在日常工作时抓耳挠腮的问题。和我们之前遇到的许多更让人恼火的问题一样,在构建镜像时这些问题往往都不是那么容易被发现,这让那些把时间花在调试这些问题上的人非常沮丧。因此,在帮其他使用Docker镜像的人调试问题时记住这些设置是值得的。

results matching ""

    No results matching ""